from math import log, sqrt
import ConfigParser

from .. import Elements

class MSMS:
	""" MSMS Object contains a single MSMS spectrum
	"""
	def __init__(self):
		self._precursorMz = 0.0
		self._precursorMass = 0.0
		self._charge = 1
		self._description = ""
		self._spectrum = []
		self._sorted = False
	def initWithList(self, list):
		""" Initialize this MSMS with a list parsed from MGF files... 
		this init is useful only with xlh.MSAnalysis.parseMgfFile method
		as takes values exactly in that way, that is a list like this:
		
		['', 'BEGIN IONS', 'PEPMASS=421.75768915154', 'CHARGE=2+',
		'TITLE=Elution from: 60.007 to 60.007 period: 0 experiment: 1 cycles: 1 precIntensity: 7324.0 FinneganScanNumber: 3265 MStype: enumIsZoomMS rawFile: f080510_021.RAW', 
		'126.94 2.1', '143.22 1.6', '149.11 5.9', '150.20 3.5', 
		'171.25 9.0', '172.91 6.4', '173.91 3.3', '187.20 2.7', 
		...]
		
		
		"""
		
		list = filter(lambda x: len(x) > 0, list)	# remove empty elements in this list
		tempSp = map(lambda x: x.split(), list[4:])	# get spectrum values
		self.setPrecursorMz(float(list[1][8:]))
		self.setCharge(int(list[2][7:8]))
		self.setDescription(list[3][6:])
		self.setSpectrum(map(lambda x: [float(x[0]), float(x[1])], tempSp))

	def sort(self):
		""" sort the spectrum from lowest to highest mass
		"""
		if not self._sorted:
			self._spectrum.sort()
			self._sorted = True
	def setPrecursorMz(self, value):
		""" set precursor m/z
		"""
		self._precursorMz = float(value)
	def precursorMz(self, r = 6):
		""" return precursor m/z
		"""
		return round(self._precursorMz, r)
	def setCharge(self, value):
		""" set precursor charge as integer
		"""
		self._charge = int(value)
		self._precursorMass = self._precursorMz * self._charge - (self._charge * Elements.getfloat("H", "mmass"))
	def charge(self):
		""" return precursor charge
		"""
		return self._charge
	def precursorMass(self, r = 4):
		""" returns precursor mass, calculated on the fly
		onche charge and m/z are defined
		"""
		return round(self._precursorMass, r)
	def length(self):
		""" return the number of peaks into this spectrum
		"""
		return len(self._spectrum)
	def setDescription(self, value):
		""" set spectrum description
		"""
		self._description = value.strip()
	def description(self):
		""" return spectrum description
		"""
		return self._description
	def addPeak(self, peakArray):
		""" add a single peak to spectrum as list
		[mass, intensity]
		"""
		self._spectrum.append(map(lambda x: float(x), peakArray))
		# each peak is a two-element array, m/z and intensity, float values
		# the spectrum is a two-dimensions array
	def addRawPeak(self, peak):
		""" add a single peak as given
		"""
		self._spectrum.append(peak)
	def setSpectrum(self, list):
		""" set a list of peaks as spectrum
		for this instance
		"""
		self._spectrum = list
	def update(self, s):
		""" update this spectrum with another MSMS instance by
		joining their peaks
		"""
		if isinstance(s, MSMS):
			self._spectrum += s._spectrum
			self.sort()
	def items(self):
		""" return an iterator over peaks
		"""
		self.sort()
		return iter(self._spectrum)
	def peaks(self):
		""" return peaks as mass/intensity list
		"""
		self.sort()
		return self._spectrum
	def peakMasses(self):
		""" return peak masses only
		"""
		self.sort()
		return map(lambda x: x[0], self._spectrum)
	def peakIntensities(self):
		""" reutrn peak intensities only
		"""
		self.sort()
		return map(lambda x: x[1], self._spectrum)
	def smallestPeak(self):
		""" return the smallest peak
		"""
		self.sort()
		return self._spectrum[0]
	def largestPeak(self):
		""" return larges peak
		"""
		self.sort()
		return self._spectrum[-1]
	def maxIntensity(self):
		""" returns the highest intensity value
		"""
		try:
			return max(map(lambda x: x[1], self._spectrum))
		except ValueError:
			print self._spectrum
	def minIntensity(self):
		""" return the lowest intensity value
		"""
		return min(map(lambda x: x[1], self._spectrum))
	def spectrumSlice(self, min = False, max = False):
		""" return a new MSMS() object that is a slice
		of current spectrum. min and max values should be given
		as m/z boundaries
		"""

		if not min:
			min = self.smallestPeak()[0]
		if not max:
			max = self.largestPeak()[0]

		self.sort()
		r = MSMS()

		r._spectrum = filter(lambda x: x[0] >= min and x[0] <= max, self.peaks())
		r.setPrecursorMz(self._precursorMz)
		r.setCharge(self._charge)
		r.setDescription(self._description)
		return r
	def closeTo(self, mass, tolerance=0.5, unit = 'Da'):
		""" return a MSMS() object with peaks close to a given
		m/z value within a tolerance
		"""
		close = MSMS()

		tolerance = float(tolerance)

		if unit == 'Da':
			None
		elif unit == 'ppm':
			tolerance = mass / 1000000 * tolerance
		else:
			return None

		close._spectrum = filter(lambda x: abs(mass - x[0]) <= tolerance, self._spectrum)
		close.setPrecursorMz(self._precursorMz)
		close.setCharge(self._charge)
		close.setDescription(self._description)
		return close
		
	def closestTo(self, mass):
		""" return the peak closest to a given mass
		"""
		m = 500000.0
		diff = 0.0
		pDiff = 500000.0
		pI = []
		for i in self._spectrum:
			diff = abs(mass - i[0])
			if diff <= m:
				m = diff
			if m >= pDiff:
#				pI = i
				break
			pDiff = diff
			pI = i
		return pI
	def filterOnIntensity(self, threshold = 0):
		""" return a new MSMS() object from current one with peaks 
		with intensity higher than a given threshold
		"""
		filtered = MSMS()
		filtered.setPrecursorMz = self._precursorMz
		filtered.setCharge = self._charge
		filtered.setDescription = self._description
		filtered._spectrum = filter(lambda x: x[1] >= threshold, self._spectrum)
		return filtered
	def mostIntense(self, n = 1):
		""" return a new MSMS() object with n most intense peak.
		
		example:
		
		for i in range(s.smallestPeak()[0], s.largestPeak()[0], 100):
			s.spectrumSlice(i, i + 100).mostIntense(3)
			
		iterates on curren spectrum and returns the 
		3 most intense peaks every 100 dalton
		"""
		filtered = MSMS()
		s = []
		filtered.setPrecursorMz = self._precursorMz
		filtered.setCharge = self._charge
		filtered.setDescription = self._description
		s = map(lambda x: [x[1], x[0]], self.items())
		s.sort()
		filtered._spectrum = s[-n:]
		filtered._spectrum = map(lambda x: [x[1], x[0]], filtered._spectrum)
		return filtered
	def entropy(self):
		""" calculate and return spectrum entropy
		"""
		entropy = 0.0
		intSum = sum(map(lambda x: x[1], self._spectrum))
		for i in self._spectrum:
			entropy = entropy + i[1]/intSum * log(i[1]/intSum)
		return entropy
	def mzRange(self):
		""" return the mass range of this spectrum
		"""
		return (self._spectrum[0][0], self._spectrum[-1][0])
	def centroid(self, tolerance = 0.5, unit = 'Da'):
		""" Calculate centroids of given spectra
			*** This function is only a placeholder ***"
		"""
		pass
	def reduceNoise(self, ratio):
		""" Cut every peak that has intensity lower that a
		certain ratio to the max intensity peak
		"""
		maxInt = self.maxIntensity()
		self._spectrum = self.filterOnIntensity(ratio * maxInt).peaks()
		